﻿using System;
using System.Linq;
using System.Threading;
using System.Configuration;
using System.Runtime.Serialization;
using System.Diagnostics;
using System.Collections.Generic;

namespace MultiViewer.Network
{
    public delegate object MessageHandler<T>(T message);
    public delegate void NotificationHandler<T>(T notification);

    public delegate void NodeConnectionHandler(int nodeId);

    public abstract class Node
    {
        protected int nodeId;
        protected int nodeCount;
        protected bool inited = false;

        protected int[] allNodes = null;
        protected int[] clientNodes = null;

        private Dictionary<Type, Delegate> messageHandlers = new Dictionary<Type,Delegate>();
        private Dictionary<Type, Delegate> notificationHandlers = new Dictionary<Type, Delegate>();

        public event NodeConnectionHandler NodeConnected;
        public event NodeConnectionHandler NodeDisconnected;

        public static Node Start(params string[] args)
        {
            string nts = GetParameter("multiViewerNodeType","mvtype",args);
            if (nts == "server")
                return new ServerNode(args);
            else if (nts == "client")
                return new ClientNode(args);
            else
                throw new Exception("Node type is not specified neither in config nor in command line");
        }

        public virtual void  Stop()
        {
            inited = false;
        }

        /// <summary>Gets node ID</summary>
        /// <remarks>Node ID is between 0 and NodeCount - 1</remarks>
        public int NodeID
        {
            get {
                if (!inited)
                    throw new InvalidOperationException("Cannot get node ID when network is not initialized");
                return nodeId; 
            }
        }

        /// <summary>Gets node count</summary>
        public int NodeCount
        {
            get
            {
                if (!inited)
                    throw new InvalidOperationException("Cannot get node count when network is not initialized");
                return nodeCount;
            }
        }

        protected static string GetParameter(string longName,string shortName,params string[] args)
        {
            string key = "/" + shortName + "=";
            if (args.Any(p => p.StartsWith(key)))
            {
                return args.Last(p => p.StartsWith(key)).Substring(shortName.Length + 2);
            }
            key = "/" + longName + "=";
            if (args.Any(p => p.StartsWith(key)))
            {
                return args.Last(p => p.StartsWith(key)).Substring(longName.Length + 2);
            }
            AppSettingsReader apr = new AppSettingsReader();
            try
            {
                return (string)apr.GetValue(longName, typeof(string));
            }
            catch // Value in config file is not found
            {
                return null; 
            }
        }

        public int[] AllNodes
        {
            get
            {
                if (allNodes == null)
                {
                    allNodes = new int[nodeCount];
                    for (int i = 0; i < nodeCount; i++)
                        allNodes[i] = i;
                }
                return allNodes;
            }
        }

        public int[] ClientNodes
        {
            get
            {
                if (clientNodes == null)
                {
                    clientNodes = new int[nodeCount-1];
                    for (int i = 1; i < nodeCount; i++)
                        clientNodes[i-1] = i;
                }
                return clientNodes;
            }
        }

        public abstract int[] ConnectedNodes
        {
            get;
        }

        public void AddMessageHandler<T>(MessageHandler<T> handler)
        {
            Type key = typeof(T);
            Delegate oldHandler;
            if (messageHandlers.TryGetValue(key, out oldHandler))
                messageHandlers[key] = Delegate.Combine(oldHandler, handler);
            else
                messageHandlers.Add(key, handler);
        }

        public void AddNotificationHandler<T>(NotificationHandler<T> handler)
        {
            Type key = typeof(T);
            Delegate oldHandler;
            if (notificationHandlers.TryGetValue(key, out oldHandler))
                notificationHandlers[key] = Delegate.Combine(oldHandler, handler);
            else
                notificationHandlers.Add(key, handler);
        }

        /// <summary>Sends message to specified nodes</summary>
        /// <param name="nodes">Array of node ids</param>
        /// <param name="request">Message to send</param>
        /// <returns>Array of responses. Number of responses equals number of nodes</returns>
        public abstract Response[] Send(int[] nodes,Message request);

        public Response[] Send(Message request)
        {
            return Send(AllNodes, request);
        }

        /// <summary>Sends message to specified nodes</summary>
        /// <param name="nodes">Array of node ids</param>
        /// <param name="request">Message to send</param>
        /// <remarks>Does not receive any result</remarks>
        public abstract void Notify(int[] nodes, Message request);

        public void Notify(Message request)
        {
            Notify(AllNodes, request);
        }

        /// <summary>Method is invoked when message arrives</summary>
        /// <param name="sender">Id of node that sends message</param>
        /// <param name="request">Message itself</param>
        /// <returns>Message processing result</returns>
        public virtual object HandleMessage(Message request)
        {
            Delegate handler;
            if (messageHandlers.TryGetValue(request.GetType(), out handler))
                return handler.DynamicInvoke(request);
            Trace.WriteLine("MultiViewer.Node: no handler is found for message type " + request.GetType().Name);
            return null;
        }

        /// <summary>Method is invoked when notification arrives</summary>
        /// <param name="sender">Id of node that sends message</param>
        /// <param name="request">Message itself</param>
        public virtual void HandleNotification(Message request)
        {
            Delegate handler;
            if (notificationHandlers.TryGetValue(request.GetType(), out handler))
            {
                handler.DynamicInvoke(request);
                return;
            }
            Trace.WriteLine("MultiViewer.Node: no handler is found for notification type " + request.GetType().Name);
        }

        protected virtual void OnNodeConnected(int nodeId)
        {
            if (NodeConnected != null)
                NodeConnected(nodeId);
        }

        protected virtual void OnNodeDisconnected(int nodeId)
        {
            if (NodeDisconnected != null)
                NodeDisconnected(nodeId);
        }
    }
}